<?php

/***
 * 
 * Bridge 桥梁模式
 * 桥接（Bridge）是用于把抽象化与实现化解耦，使得二者可以独立变化。这种类型的设计模式属于结构型模式，它通过提供抽象化和实现化之间的桥接结构，来实现二者的解耦。
 
 这种模式涉及到一个作为桥接的接口，使得实体类的功能独立于接口实现类。这两种类型的类可被结构化改变而互不影响。
 
 我们通过下面的实例来演示桥接模式（Bridge Pattern）的用法。其中，可以使用相同的抽象类方法但是不同的桥接实现类，来画出不同颜色的圆。
 
 介绍
 意图：将抽象部分与实现部分分离，使它们都可以独立的变化。
 
 主要解决：在有多种可能会变化的情况下，用继承会造成类爆炸问题，扩展起来不灵活。
 
 何时使用：实现系统可能有多个角度分类，每一种角度都可能变化。
 
 如何解决：把这种多角度分类分离出来，让它们独立变化，减少它们之间耦合。
 
 关键代码：抽象类依赖实现类。
 
 应用实例：1、猪八戒从天蓬元帅转世投胎到猪，转世投胎的机制将尘世划分为两个等级，即：灵魂和肉体，前者相当于抽象化，后者相当于实现化。生灵通过功能的委派，调用肉体对象的功能，使得生灵可以动态地选择。 2、墙上的开关，可以看到的开关是抽象的，不用管里面具体怎么实现的。
 
 优点：1、抽象和实现的分离。 2、优秀的扩展能力。 3、实现细节对客户透明。
 
 缺点：桥接模式的引入会增加系统的理解与设计难度，由于聚合关联关系建立在抽象层，要求开发者针对抽象进行设计与编程。
 
 使用场景：1、如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性，避免在两个层次之间建立静态的继承联系，通过桥接模式可以使它们在抽象层建立一个关联关系。 2、对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统，桥接模式尤为适用。 3、一个类存在两个独立变化的维度，且这两个维度都需要进行扩展。
 
 注意事项：对于两个独立变化的维度，使用桥接模式再适合不过了。
 */

/**
 * 创建格式化接口。
 */
interface FormatterInterface
{

    public function format(string $text);
}

class PlainTextFormatter implements FormatterInterface
{

    /**
     * 返回字符串格式。
     */
    public function format(string $text)
    {
        return $text;
    }
}

class HtmlFormatter implements FormatterInterface
{

    /**
     * 返回 HTML 格式。
     */
    public function format(string $text)
    {
        return sprintf('<p>%s</p>', $text);
    }
}

/**
 * 创建抽象类 Service。
 */
abstract class Service
{

    /**
     *
     * @var FormatterInterface 定义实现属性。
     */
    protected $implementation;

    /**
     *
     * @param FormatterInterface $printer
     *            传入 FormatterInterface 实现类对象。
     */
    public function __construct(FormatterInterface $printer)
    {
        $this->implementation = $printer;
    }

    /**
     *
     * @param FormatterInterface $printer
     *            和构造方法的作用相同。
     */
    public function setImplementation(FormatterInterface $printer)
    {
        $this->implementation = $printer;
    }

    /**
     * 创建抽象方法 get() 。
     */
    abstract public function get();
}

/**
 * 创建 Service 子类 HelloWorldService 。
 */
class HelloWorldService extends Service
{

    /**
     * 定义抽象方法 get() 。
     * 根据传入的格式类定义来格式化输出 'Hello World' 。
     */
    public function get()
    {
        return $this->implementation->format('Hello World');
    }
}
$service = new HelloWorldService(new PlainTextFormatter());
$service->get();

// 现在更改实现方法为使用 HTML 格式器。
$service->setImplementation(new HtmlFormatter());
$service->get();

// 场景复现
// 小喜在做一个平台（阿尔法）的本地 SDK 工具，用于线下开发模块，这个工具的用户是 A 部门的前端人员，经过一个星期的紧张开发，小喜完成了工具的开发，用户反馈不错。大致的代码结构是这个样子的：

// class SDK {
// login() { /* 登录控制 */ }
// start() { /* 启动逻辑 */ }
// create() { /* 创建模块 */ }
// publish() { /* 提交模块 */ }
// init() {
// this.login();
// this.start();
// this.create();
// this.publish();
// }
// }
// 突然有一天，老大小苏说，最近公司资源紧缺，雇了十几个外包，也要用这个 SDK 工具开发模块，而外包是不允许访问公司内网的。工程紧急，小苏要求小喜在一天内为外包同学搞一个可以用的 SDK，小喜想了想，点了点头，花了不到半天就搞定了：

// class wbSDK extend SDK {
// wbLogin() { /*外包登录*/ }
// //...
// }
// 准备交差的时候，小苏提醒道，外包是不允许直接提交模块的，必须有一个审核流程，于是小喜回去又修改了一下：

// class wbSDK extend SDK {
// wbLogin() { /*外包登录*/ }
// wbPublish() {
// if (this.checkPublish() {
// this.publish();
// }
// }
// //...
// }
// 一段时间之后，这个平台影响力做大了，部门 B、C、D 的同学都想使用这个工具，小喜此时觉得蛋有点疼，原因是每个部门的登录、检测和发布都有差异，为了让更多人受益于这个 SDK 工具，小喜只好硬着头皮写了这些代码：

// class B_SDK extend SDK {
// B_Login() {}
// B_Check() {}
// B_Publish() {}
// //...
// }
// class C_SDK extend SDK {
// C_Login() {}
// C_Check() {}
// C_Publish() {}
// //...
// }
// // ...
// 部门 F 的同学因为某种原因，也搞了一个类似的平台（里试石），而且做得比阿尔法要好，于是部门老板要求小喜将 SDK 对接到里试石，两个系统主要的差异在于模块的初始化和创建形式不同，而且这些差异在不同的部门之间也有不同。小喜擦干了眼泪，代码一直撸到天亮：

// class baseSDK extent SDK {
// // base SDK
// }
// class Fuck_A_SDK extend baseSDK {}
// class Fuck_B_SDK extend baseSDK {}
// class Fuck_C_SDK extend baseSDK {}
// //....
// 问题解析
// 以上问题的根源在于 影响 SDK 这个类的变量太多了。刚开始影响 SDK 的变量是登录，小喜在代码中添加了 wbLogin 方法，然后在代码中写了一堆 if else 逻辑，勉强解决问题。

// 后面影响的因素变得很多，有登录、初始化、检测、发布以及部门和平台差异等，小喜通过多次继承来解决问题，带来的麻烦是，代码的维护成本和冗余度都变高了。

// 小喜把这个问题丢给了小天，询问有没有更好的处理办法，小天说，刚开始 Login 独立出来一个类，不同部门和外包可以基于 baseLogin 扩展…小喜打断道，你丫的事后诸葛亮，我怎么知道会有外包和其他部门的同学用我的 SDK 呀 🤣🤣🤣

// 小天的想法是正确的，如果我们能够早早的探测到变量，将变量通过类抽象出来，那么使用的时候就会轻松很多了：

// class baseSDK {}

// // 抽象变量
// class baseLogin {}
// class baseCreate {}
// class baseCheck {}

// // C 部门使用示例
// class C_Login extent baseLogin {}
// class C_Create extent baseCreate {}
// class C_Check extent baseCheck {}

// // 启动程序
// runSDK = () => {
// let sdk = new baseSDK();
// sdk.setLogin(new C_Login());
// sdk.setCreate(new C_Create());
// sdk.setCheck(new C_Check());
// sdk.init();
// }
// 桥接模式
// 桥接模式的思想就是：将抽象部分与实现部分分离，使它们都可以独立地变化。

// 分离抽象部分，要尽可能多的考虑哪些是变量，哪些是常量，常量可以写死在代码中，而变量就需要抽象出来交给管理器去管理，尤其是十分复杂的变量（比如登录这个操作）。

// 在桥接模式中，每一个被抽象出来的变量，都需要在主体中提供一个渠道——也就是「桥」——来衔接，比如上面代码中的 sdk.setLogin() 方法，它的作用就是连接 new C_Login()。这应该就是为什么这个模式被命名为 Bridge 的原因吧。

//S.O.L.I.D 五项原则中有一条是单一职责原则，意思就是一个类只应该有一
